/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>

#include "debughelper/debughelper.h"
#include "logger/logger.h"
#include "datachannel/clsocketchannel.h"
#include "datachannel/filechannel.h"
#include "datachannel/pcapchannel.h"
#include "datachannel/rawsocketchannel.h"
#include "datachannel/datachannel.h"
#include "datachannel/datachannelfactory.h"


using namespace datachannel;


const std::string DataChannelFactory::FILE_DESCRIPTOR("file://");
const std::string DataChannelFactory::PCAP_DESCRIPTOR("pcap://");
const std::string DataChannelFactory::SOCKET_DESCRIPTOR("sock://");
const std::string DataChannelFactory::RAW_SOCKET_ATTRIBUTE(":raw");

/**
 ****************************************************************************
 * @brief  Opens the appropriate data channel.
 *
 *  The descriptor prefix determines the type of data channel to instanciate.
 *
 *   Prefix     DataChannel type
 *   file://    FileChannel
 *   pcap://    PcapChannel
 *   sock://    SocketChannel
 ****************************************************************************
**/
DataChannel *DataChannelFactory::getInstance(const std::string &descriptor,
                                             logger::Logger *logger) {
    DataChannel *channel = NULL;

    if (logger != NULL) {
        if (descriptor.find(FILE_DESCRIPTOR) == 0) {
            channel = new FileChannel(logger);
            if (channel != NULL) {
                if (!channel->open(
                    descriptor.substr(FILE_DESCRIPTOR.length()))) {
                    delete channel;
                    channel = NULL;
                }
            }
        } else if (descriptor.find(PCAP_DESCRIPTOR) == 0) {
            channel = new PcapChannel(logger);
            if (channel != NULL) {
                if (!channel->open(
                    descriptor.substr(PCAP_DESCRIPTOR.length()))) {
                    delete channel;
                    channel = NULL;
                }
            }
        } else if (descriptor.find(SOCKET_DESCRIPTOR) == 0) {
            if (descriptor.find(RAW_SOCKET_ATTRIBUTE) == std::string::npos) {
                channel = new CLSocketChannel(logger);
            } else {
                channel = new RawSocketChannel(logger);
            }
            if (channel != NULL) {
                if (!channel->open(
                    descriptor.substr(SOCKET_DESCRIPTOR.length()))) {
                    delete channel;
                    channel = NULL;
                }
            }
        }
    }

    return channel;
}

/**
 ****************************************************************************
 * @brief  Performs a self test of this class using the specified descriptors
 *
 * @return True if all tests passed
 ****************************************************************************
**/
bool DataChannelFactory::unitTest(const std::string &inDescriptor,
                                  const std::string &outDescriptor,
                                  const std::string &text,
                                  logger::Logger *logger) {
    bool flag = true;

    int n1   = 0;
    int n2   = 0;
    int nIn  = 0;
    int nOut = 0;
    char *inBuffer = new char[1024];
    const char *buffer1 = "Hello!";
    const char *buffer2 = "Goodbye";
    DataChannel *inChannel = NULL;
    DataChannel *outChannel = NULL;
    n1 = strlen(buffer1) + 1;
    n2 = strlen(buffer2) + 1;

    // create test sockets
    inChannel = DataChannelFactory::getInstance(inDescriptor, logger);
    outChannel = DataChannelFactory::getInstance(outDescriptor, logger);

    // send/receive test
    nOut = outChannel->write(buffer1, n1);
    nIn  = inChannel->read(inBuffer, 1024);
    if ((nOut != n1) || (nIn != n1)) {
        logger->fatal(
            "DataChannelFactory.unitTest: %s send mismatch: %d != %d != %d",
            text.c_str(), nOut, nIn, n1);
        flag = false;
    } else if (memcmp(buffer1, inBuffer, nIn) != 0) {
        logger->fatal(
            "DataChannelFactory.unitTest: %s send mismatch", text.c_str());
        flag = false;
    }

    // reply/receive test
    nOut = inChannel->write(buffer2, n2);
    nIn  = outChannel->read(inBuffer, 1024);
    if ((nOut != n2) || (nIn != n2)) {
        logger->fatal(
            "DataChannelFactory.unitTest: %s reply mismatch: %d != %d != %d",
            text.c_str(), nOut, nIn, n2);
        flag = false;
    } else if (memcmp(buffer2, inBuffer, nIn) != 0) {
        logger->fatal(
            "DataChannelFactory.unitTest: %s reply mismatch", text.c_str());
        flag = false;
    }

    // no receive test
    nIn  = inChannel->read(inBuffer, 1024);
    nOut = outChannel->read(inBuffer, 1024);
    if ((nOut != 0) || (nIn != 0)) {
        logger->fatal(
        "DataChannelFactory.unitTest: %s should not have received data: %d/%d",
        text.c_str(), nIn, nOut);
        flag = false;
    }

    outChannel->close();
    inChannel->close();

    delete outChannel;
    outChannel = NULL;
    delete inChannel;
    inChannel = NULL;

    delete []inBuffer;
    inBuffer = NULL;

    return flag;
}

/**
 ****************************************************************************
 * @brief  Performs a self test of this class
 *
 * @return True if all tests passed
 ****************************************************************************
**/
bool DataChannelFactory::unitTest() {
    bool flag = true;

    logger::Logger::initialize("DataChannelFactory");
    logger::Logger *logger = logger::Logger::getInstance();

    // udp socket test
    flag &= unitTest("sock://255.255.255.255:54321:udp:passive",
                     "sock://127.0.0.1:54321:udp:active",
                     "UDP", logger);

    // tcp socket test
    flag &= unitTest("sock://255.255.255.255:12345:tcp:passive",
                     "sock://127.0.0.1:12345:tcp:active",
                     "TCP", logger);

    logger->info("DataChannelFactory.unitTest %s\n",
        (flag) ? "Passed" : "Failed");

    logger::Logger::cleanup();

    return flag;
}
